{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

module Cardano.Testnet.Test.Gov.Transaction.HashMismatch
  ( hprop_transaction_build_wrong_hash
  ) where

import           Cardano.Api as Api

import           Cardano.Testnet

import           Prelude

import           Control.Monad
import           Data.Default.Class
import qualified Data.Text as Text
import           GHC.IO.Exception (ExitCode (ExitFailure))
import           System.Directory (makeAbsolute)
import           System.FilePath ((</>))

import           Test.Cardano.CLI.Hash (serveFilesWhile, tamperBase16Hash)
import           Testnet.Components.Query
import           Testnet.Process.Cli.Keys
import           Testnet.Process.Run (addEnvVarsToConfig, execCli', execCliAny, mkExecConfig)
import           Testnet.Property.Util (integrationRetryWorkspace)
import           Testnet.Start.Types
import           Testnet.Types

import           Hedgehog
import qualified Hedgehog as H
import qualified Hedgehog.Extras as H

-- | Execute me with:
-- @DISABLE_RETRIES=1 cabal test cardano-testnet-test --test-options '-p "/Transaction Build Wrong Hash/'@
hprop_transaction_build_wrong_hash :: Property
hprop_transaction_build_wrong_hash = integrationRetryWorkspace 2 "wrong-hash" $ \tempAbsBasePath' -> H.runWithDefaultWatchdog_ $ do

  conf@Conf { tempAbsPath } <- H.noteShowM $ mkConf tempAbsBasePath'
  let tempAbsPath' = unTmpAbsPath tempAbsPath
      tempBaseAbsPath = makeTmpBaseAbsPath tempAbsPath

  work <- H.createDirectoryIfMissing $ tempAbsPath' </> "work"

  let ceo = ConwayEraOnwardsConway
      sbe = convert ceo
      asbe = AnyShelleyBasedEra sbe
      eraName = eraToString sbe
      fastTestnetOptions = def { cardanoNodeEra = asbe }
      shelleyOptions = def { genesisEpochLength = 200 }

  TestnetRuntime
    { testnetMagic
    , testnetNodes
    , wallets=wallet0:wallet1:_
    , configurationFile
    }
    <- createAndRunTestnet fastTestnetOptions shelleyOptions conf

  node <- H.headM testnetNodes
  poolSprocket1 <- H.noteShow $ nodeSprocket node
  execConfig <- mkExecConfig tempBaseAbsPath poolSprocket1 testnetMagic
  let socketPath = nodeSocketPath node

  epochStateView <- getEpochStateView configurationFile socketPath

  H.note_ $ "Sprocket: " <> show poolSprocket1
  H.note_ $ "Abs path: " <> tempAbsBasePath'
  H.note_ $ "Socketpath: " <> unFile socketPath
  H.note_ $ "Foldblocks config file: " <> unFile configurationFile

  gov <- H.createDirectoryIfMissing $ work </> "governance"

  let proposalAnchorDataIpfsHash = "QmexFJuEn5RtnHEqpxDcqrazdHPzAwe7zs2RxHLfMH5gBz"
  proposalAnchorFile <- H.noteM $ liftIO $ makeAbsolute $ "test" </> "cardano-testnet-test" </> "files" </> "sample-proposal-anchor"

  infoActionFp <- H.note $ work </> gov </> "info.action"

  proposalAnchorDataHash <- execCli' execConfig
    [ "hash", "anchor-data", "--file-binary", proposalAnchorFile
    ]

  let stakeVkeyFp = gov </> "stake.vkey"
      stakeSKeyFp = gov </> "stake.skey"
      stakeCertFp = gov </> "stake.regcert"
      stakeKeys =  KeyPair { verificationKey = File stakeVkeyFp
                           , signingKey = File stakeSKeyFp
                           }

  cliStakeAddressKeyGen stakeKeys

  -- Register stake address
  keyDeposit <- getKeyDeposit epochStateView ceo

  void $ execCli' execConfig
    [ eraName, "stake-address", "registration-certificate"
    , "--stake-verification-key-file", stakeVkeyFp
    , "--key-reg-deposit-amt", show $ unCoin keyDeposit
    , "--out-file", stakeCertFp
    ]

  stakeCertTxBodyFp <- H.note $ work </> "stake.registration.txbody"
  stakeCertTxSignedFp <- H.note $ work </> "stake.registration.tx"

  txin1 <- findLargestUtxoForPaymentKey epochStateView sbe wallet0

  void $ execCli' execConfig
    [ eraName, "transaction", "build"
    , "--change-address", Text.unpack $ paymentKeyInfoAddr wallet0
    , "--tx-in", Text.unpack $ renderTxIn txin1
    , "--tx-out", Text.unpack (paymentKeyInfoAddr wallet1) <> "+" <> show @Int 10_000_000
    , "--certificate-file", stakeCertFp
    , "--witness-override", show @Int 2
    , "--out-file", stakeCertTxBodyFp
    ]

  void $ execCli' execConfig
    [ eraName, "transaction", "sign"
    , "--tx-body-file", stakeCertTxBodyFp
    , "--signing-key-file", signingKeyFp $ paymentKeyInfoPair wallet0
    , "--signing-key-file", stakeSKeyFp
    , "--out-file", stakeCertTxSignedFp
    ]

  void $ execCli' execConfig
    [ eraName, "transaction", "submit"
    , "--tx-file", stakeCertTxSignedFp
    ]

  let relativeUrl = ["ipfs", proposalAnchorDataIpfsHash]

  txbodyFp <- H.note $ work </> "tx.body"

  tamperedHash <- H.evalMaybe $ tamperBase16Hash proposalAnchorDataHash

  -- Create temporary HTTP server with files required by the call to `cardano-cli`
  -- In this case, the server emulates an IPFS gateway
  serveFilesWhile
    [(relativeUrl, proposalAnchorFile)]
    ( \port -> do
        let execConfig' = addEnvVarsToConfig execConfig [("IPFS_GATEWAY_URI", "http://localhost:" ++ show port ++ "/")]

        minDepositAmount <- getMinGovActionDeposit epochStateView ceo

        void $
          execCli'
            execConfig'
            [ eraName, "governance", "action", "create-info"
            , "--testnet"
            , "--governance-action-deposit", show minDepositAmount
            , "--deposit-return-stake-verification-key-file", stakeVkeyFp
            , "--anchor-url", "ipfs://" ++ proposalAnchorDataIpfsHash
            , "--anchor-data-hash", tamperedHash
            , "--out-file", infoActionFp
            ]

        txin2 <- findLargestUtxoForPaymentKey epochStateView sbe wallet1

        (exitCode, _, stderrOutput) <-
          execCliAny
            execConfig'
            [ eraName, "transaction", "build"
            , "--change-address", Text.unpack $ paymentKeyInfoAddr wallet1
            , "--tx-in", Text.unpack $ renderTxIn txin2
            , "--tx-out", Text.unpack (paymentKeyInfoAddr wallet0) <> "+" <> show @Int 5_000_000
            , "--proposal-file", infoActionFp
            , "--out-file", txbodyFp
            ]

        exitCode H.=== ExitFailure 1

        H.note_ stderrOutput

        H.assertWith (Text.pack stderrOutput) ("Hashes do not match!" `Text.isInfixOf`)
    )
